from base import Page

__author__ = 'thanatos'

import logging;

logging.basicConfig(level=logging.INFO)
import asyncio
import exception as exception
import dao


class Field(object):
    domains = {}

    def __init__(self, name, column_type, primary_key, default, transfer=None):
        """
        :param name: what's name in database
        :param column_type: what's name in table in database
        :param primary_key: is it primary key?
        :param default: default value
        :return:
        """
        self.name = name
        self.column_type = column_type
        self.primary_key = primary_key
        self.default = default
        self.transfer = transfer or (lambda v: v if v is None else str(v))


class StringField(Field):
    def __init__(self, name=None, length=20, primary_key=False, default=None):
        super().__init__(name, 'varchar(%s)' % length, primary_key, default)


class TextField(Field):
    def __init__(self, name=None, default=None):
        super().__init__(name, 'longtext', False, default)


class BooleanField(Field):
    def __init__(self, name=None, primary_key=False, default=False):
        super().__init__(name, 'boolean', primary_key, default, transfer=lambda v: v if v is None else bool(ord(v)))


class LongField(Field):
    def __init__(self, name=None, primary_key=False, default=None):
        super().__init__(name, 'long', primary_key, default, transfer=lambda v: v if v is None else int(v))


class IntegerField(Field):
    def __init__(self, name=None, primary_key=False, default=None):
        super().__init__(name, 'int', primary_key, default, transfer=lambda v: v if v is None else int(v))


class FloatField(Field):
    def __init__(self, name=None, primary_key=False, default=None):
        super().__init__(name, 'float', primary_key, default, transfer=lambda v: v if v is None else float(v))


class ModelField(Field):
    """Many To One
    """

    def __init__(self, model, name=None, primary_key=False, default=None):
        self.model = model
        super().__init__(name, 'long', primary_key, default, transfer=self.transfer)

    @asyncio.coroutine
    def transfer(self, mid):
        if isinstance(self.model, str):
            self.model = getattr(__import__(Field.domains[self.model], globals(), locals()), self.model)
        if not isinstance(self.model, ModelMetaClass):
            raise ValueError('%s is not subclass of Model' % self.model)
        return (yield from self.model.find(mid))


class MultiField(Field):
    """Many To Many or One To Many
    """

    def __init__(self, mapped_key, model, another_key=None, table=None):
        """
        :param mapped_key: One 的一方
        :param another_key: Many的一方
        :param model: Many的一方对应的实体
        :param table: 第三方表，没有表示One To Many
        :return:
        """
        self.model = model
        self.table = table
        self.mapped_key = mapped_key
        self.another_key = another_key
        super().__init__(None, 'long', False, None, self.transfer)

    @asyncio.coroutine
    def transfer(self, mid):
        if isinstance(self.model, str):
            self.model = getattr(__import__(Field.domains[self.model], globals(), locals()), self.model)
        if not isinstance(self.model, ModelMetaClass):
            raise ValueError('%s is not subclass of Model' % self.model)
        if self.table and self.another_key:
            sql = 'SELECT r.* FROM %s AS ur JOIN %s AS r ON r.%s = ur.%s AND ur.%s=?' % \
                  (self.table, self.model.__table__, self.model.__key__, self.another_key, self.mapped_key)
        else:
            sql = 'SELECT * FROM %s WHERE %s=?' % (self.model.__table__, self.mapped_key)
        return self.model.select(mid, sql=sql, fetchable=False, cascade=False)


class ModelMetaClass(type):
    def __new__(mcs, name, bases, attrs):
        if name == 'Model':
            return type.__new__(mcs, name, bases, attrs)
        Field.domains[name] = attrs['__module__']
        table = attrs.get('__table__', None) or name
        mappings = dict()
        field_mappings = dict()
        primary_key = None
        key = None
        for k, v in attrs.items():
            if isinstance(v, Field):
                mappings[k] = v
                v.name = v.name or k
                if v.primary_key:
                    if primary_key:
                        raise exception.DuplicateError('There has Duplicate primary key')
                    primary_key = v
                    key = k
                else:
                    if not isinstance(v, MultiField):
                        field_mappings[k] = v.name
        if not primary_key:
            raise ValueError("excepting a primary key but got nothing")
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings
        attrs['__field_mappings__'] = field_mappings
        attrs['__table__'] = table
        attrs['__key__'] = key
        attrs['__primary_key__'] = primary_key
        attrs['__save__'] = 'INSERT INTO %s(%s) VALUES(%s)' % (
            table, ','.join(field_mappings.values()), ','.join('?' * len(field_mappings)))
        attrs['__delete__'] = 'DELETE FROM %s WHERE %s=?' % (table, primary_key.name)
        attrs['__select__'] = 'SELECT * FROM %s' % table
        attrs['__count__'] = 'SELECT COUNT(*) AS count FROM %s' % table
        attrs['__unique__'] = 'SELECT * FROM %s WHERE %s=?' % (table, primary_key.name)
        attrs['__update__'] = 'UPDATE %s' % table
        attrs['__update_all__'] = 'UPDATE %s SET %s WHERE %s=?' % (
            table, ','.join(list(map(lambda f: '%s=?' % f, field_mappings.values()))), primary_key.name)
        return type.__new__(mcs, name, bases, attrs)


class Model(dict, metaclass=ModelMetaClass):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def __getattr__(self, item):
        try:
            return self[item]
        except KeyError:
            return None

    def __setattr__(self, key, value):
        self[key] = value

    def __str__(self):
        return self.__class__.__name__ + '{' + ', '.join(
            list(map(lambda k: '%s: %s' % (k, self.getValue(k)), self.__mappings__.keys()))) + '}'

    __repr__ = __str__

    def remove(self, *args):
        """
        删除指定的字段
        :param args:
        :return:
        """
        for k in args:
            try:
                del self[k]
            except KeyError:
                logging.warning('There has not this key here')

    def reserve(self, *args):
        """
       只留下需要的属性
        :param args:
        :return:
        """
        for k in self.__mappings__.keys():
            if k not in args and getattr(self, k, None) is not None:
                del self[k]

    def getValue(self, key):
        return getattr(self, key, None)

    def getValueOfDefault(self, key):
        value = self.getValue(key)
        if value is None:
            field = self.__mappings__[key]
            if field is not None and field.default is not None:
                value = field.default
                self.__setattr__(key, value)
        return value

    def extend(self, **kwargs):
        for k, v in kwargs.items():
            self[k] = v

    @asyncio.coroutine
    def save(self):
        args = list(map(self.getValueOfDefault, self.__field_mappings__.keys()))
        _id = yield from dao.execute(self.__save__, args, is_insert=True)
        self[self.__key__] = _id

    @asyncio.coroutine
    def prune(self):
        key_value = getattr(self, self.__primary_key__.name, None)
        if not key_value:
            raise ValueError('key value not found')
        rows = yield from dao.execute(self.__delete__, key_value)
        logging.info('prune effected :%s', rows)

    @asyncio.coroutine
    def renew(self, *fields):
        """
        用户信息更新，与dict的update方法区别开来
        :param fields:
        :return:
        """
        sql = []
        if fields:
            sql.append(self.__update__)
            sql.append('SET')
            sql.append(','.join(list(map(lambda v: '%s=?' % v, [self.__field_mappings__[k] for k in fields]))))
            sql.append('WHERE %s=?' % self.__key__)
            args = [self.getValueOfDefault(k) for k in fields] + [self.getValueOfDefault(self.__key__)]
        else:
            sql.append(self.__update_all__)
            args = [self.getValueOfDefault(k) for k in self.__field_mappings__.keys()] + [
                self.getValueOfDefault(self.__key__)]
        logging.info('update --> %s ' % ' '.join(sql))
        yield from dao.execute(' '.join(sql), args)

    @classmethod
    @asyncio.coroutine
    def select(cls, *args, sql=None, size=None, fetchable=False, cascade=False):
        rows = yield from dao.select(sql or cls.__select__, args, size)
        return (yield from cls.transfer(rows, fetchable, cascade))

    @classmethod
    @asyncio.coroutine
    def transfer(cls, kwargs, fetchable, cascade):
        o = None
        if isinstance(kwargs, dict):
            o = cls()
            for k, v in cls.__mappings__.items():
                if cascade and isinstance(v, MultiField) and kwargs.get(cls.__key__):
                    value = yield from v.transfer(kwargs.get(cls.__key__))
                    if value:
                        o[k] = value
                else:
                    value = kwargs.get(v.name)
                    if value is not None:
                        if isinstance(v, ModelField):
                            if fetchable:
                                o[k] = yield from v.transfer(value)
                        else:
                            o[k] = v.transfer(value)
        if isinstance(kwargs, list):
            o = list()
            for v in kwargs:
                value = yield from cls.transfer(v, fetchable, cascade)
                o.append(value)
        return o

    @classmethod
    @asyncio.coroutine
    def findAll(cls, *args, where=None, fetchable=False, cascade=False):
        sql = [cls.__select__]
        if where:
            sql.append('WHERE')
            sql.append(where)
        logging.info('findAll sql --> %s' % sql)
        values = yield from dao.select(' '.join(sql), args)
        return (yield from cls.transfer(values, fetchable, cascade))

    @classmethod
    @asyncio.coroutine
    def find(cls, uid, fetchable=False, cascade=False):
        if not uid:
            return None
        logging.info("find sql --> %s" % cls.__unique__)
        values = yield from dao.select(cls.__unique__, uid)
        if len(values) <= 0:
            return None
        return (yield from cls.transfer(values[0], fetchable, cascade))

    @classmethod
    @asyncio.coroutine
    def page(cls, *args, select_sql=None, count_sql=None, where=None, current=1, size=10, fetchable=False,
             cascade=False):
        sqls = []
        if count_sql:
            sqls.append(count_sql)
        else:
            sqls.append(cls.__count__)
            if where:
                sqls.append('WHERE')
                sqls.append(where)
        count = yield from dao.select(' '.join(sqls), args)
        records = count[0]['count']
        sqls = []
        if select_sql:
            sqls.append(select_sql)
        else:
            sqls.append(cls.__select__)
            if where:
                sqls.append('WHERE')
                sqls.append(where)
        sqls.append('LIMIT ?,?')
        args = args + ((current - 1) * size, size)
        values = yield from dao.select(' '.join(sqls), args)
        values = yield from cls.transfer(values, fetchable, cascade)
        return Page(current, size, records, values)

    @classmethod
    @asyncio.coroutine
    def count(cls, *args, where=None, sql=None):
        if sql is None:
            sqls = ['SELECT count(*) FROM %s' % cls.__table__]
            if where:
                sqls.append('WHERE')
                sqls.append(where)
            sql = ' '.join(sqls)
        count = yield from dao.select(sql, args)
        return count[0]['count'] if count else 0
